package com.tianji.promotion.service.impl;

import com.tianji.api.dto.promotion.CouponDiscountDTO;
import com.tianji.api.dto.trade.OrderCouponDTO;
import com.tianji.api.dto.trade.OrderCourseDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.ObjectUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountSolutionService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class DiscountSolutionServiceImpl implements IDiscountSolutionService {
    private final UserCouponMapper userCouponMapper;
    private final ICouponScopeService scopeService;

    /**
     * 查询并计算优惠券减免方案
     *
     * @param courses
     */
    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> courses) {
        //1、查询用户可用的优惠券（状态为未使用）
        Long userId = UserContext.getUser();
        List<Coupon> availableCoupons = userCouponMapper.queryAvailableCoupons(userId);
        if (ObjectUtils.isEmpty(availableCoupons)) {
            return CollUtils.emptyList();//如果用户没有可以使用的优惠券返回空集合
        }
        //2、初步筛选，过滤订单总金额不满足优惠券门槛的优惠券
        availableCoupons = availableCoupons.stream().filter(coupon -> coupon.getThresholdAmount() <= courses.stream().mapToInt(OrderCourseDTO::getPrice).sum())
                .collect(Collectors.toList());
        if (ObjectUtils.isEmpty(availableCoupons)) {
            return CollUtils.emptyList();//如果用户没有满足要求的优惠券返回空集合
        }
        //3、细筛，对应订单课程可用的优惠券
        Map<Coupon, List<OrderCourseDTO>> availableMap = getAvailableCourseMap(courses, availableCoupons);
        if (ObjectUtils.isEmpty(availableMap)) {
            return CollUtils.emptyList();//细筛后，如果用户没有对应课程的优惠券返回空集合
        }
        //4、排列组合优惠券
        availableCoupons = new ArrayList<>(availableMap.keySet());
        List<List<Coupon>> sulotions = PermuteUtil.permute(availableCoupons);
        //4.1 添加单张优惠券方案
        for (Coupon availableCoupon : availableCoupons) {
            sulotions.add(List.of(availableCoupon));
        }
        //5、遍历组合方案，计算优惠明细
        //5.1初始化List<CouponDiscountDTO>
        List<CouponDiscountDTO> discountDTOs = Collections.synchronizedList(new ArrayList<>(sulotions.size()));
        //5.2计算每个方案的优惠明细，将结果存入discountDTOs
        //定义计算锁
        CountDownLatch latch = new CountDownLatch(sulotions.size());
        for (List<Coupon> solution : sulotions) {
            //多线程异步优化
            CompletableFuture.supplyAsync(() -> calculateDiscount(solution, courses, availableMap)).thenAccept(r -> {
                discountDTOs.add(r);
                latch.countDown();
            });
        }
        try {
            //阻塞等待线程结果，超时时间1秒
            latch.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.info("计算优惠方案超时中断:{}", e);
        }
        //6、筛选最优解
        return findBestSolution(discountDTOs);
    }

    /**
     * 根据订单课程列表，结果用户选择的可用优惠券，获取对应优惠券的可用课程列表集合
     * @param courses
     * @param availableCoupons
     * @return
     */
    private Map<Coupon, List<OrderCourseDTO>> getAvailableCourseMap(List<OrderCourseDTO> courses, List<Coupon> availableCoupons) {
        Map<Coupon, List<OrderCourseDTO>> availableMap = new HashMap<>(availableCoupons.size());
        for (Coupon availableCoupon : availableCoupons) {
            //1、判断优惠券是否存在范围
            //1.1、不存在范围直接添加到availableMap
            if (!availableCoupon.getSpecific()) {
                availableMap.put(availableCoupon, courses);
                continue;
            }
            //2、获取优惠券对应的课程id列表
            List<Long> bizIds = scopeService.getBizIdsByCouponId(availableCoupon.getId());
            //2.1、过滤订单课程，获取优惠券对应的订单中课程列表
            List<OrderCourseDTO> availableCourses = courses.stream().filter(course -> bizIds.contains(course.getCateId())).collect(Collectors.toList());
            if (ObjectUtils.isEmpty(availableCourses)) {
                //如果没有可用课程，将该优惠券过滤
                continue;
            }
            boolean canUse = availableCoupon.getThresholdAmount() <= availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
            //3、如果对应课程总价满足优惠券使用门槛，添加到availableMap
            if (canUse) {
                availableMap.put(availableCoupon, availableCourses);
            }
        }
        return availableMap;
    }

    /**
     * 查询对应优惠方案明细
     *
     * @param orderCouponDTO
     */
    @Override
    public CouponDiscountDTO getDiscountDetail(OrderCouponDTO orderCouponDTO) {
        //1、获取用户优惠券ids参数和课程dto参数
        List<Long> userCouponIds = orderCouponDTO.getUserCouponIds();
        if (ObjectUtils.isEmpty(userCouponIds)) {
            return null;//未选用优惠券，直接返回null
        }
        List<OrderCourseDTO> courseList = orderCouponDTO.getCourseList();
        //2、查询用户可用优惠券
        List<Coupon> availableCoupons = userCouponMapper.queryAvailableCouponsByUserCoupons(userCouponIds);
        if (availableCoupons.size() < userCouponIds.size()) {
            //判断优惠券的时效性，如果选择的优惠券与本次结算时查询到的优惠券不一致，抛出异常
            throw new BadRequestException("存在优惠券已过期或已使用，请重新选择优惠方案");
        }
        //3、查询对应优惠券可用课程Map集合
        Map<Coupon, List<OrderCourseDTO>> availableCourseMap = getAvailableCourseMap(courseList, availableCoupons);
        if (ObjectUtils.isEmpty(availableCourseMap)){
            //如果选择的优惠券对应课程中没有可用的，直接返回null
            return null;
        }
        //4、根据用户选择的优惠券组合方案和对应优惠券可用课程Map集合，计算优惠方案明细
        return calculateDiscount(availableCoupons, courseList, availableCourseMap);
    }


    /**
     * 过滤最佳方案，剔除重复选项组合
     * 最佳方案有两种情况：
     * 1、相同优惠券数量情况下优惠金额最高
     * 2、相同优惠金额下，优惠券数量最少
     * 因此最优方案为两者并集：可能金额较低可能用券较少，最终供用户选择
     */
    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> discountDTOs) {
        //1、初始化2中情况下的最优方案Map集合
        //K:优惠券组合（无关叠加计算顺序） V:优惠方案
        Map<String, CouponDiscountDTO> bestAmountMap = new HashMap<>(discountDTOs.size());
        //K:优惠金额 V:优惠方案
        Map<Integer, CouponDiscountDTO> bestCouponsMap = new HashMap<>(discountDTOs.size());
        //2、遍历所有方案，筛选出最优方案分情况存入2个map集合中
        for (CouponDiscountDTO discountDTO : discountDTOs) {
            //2.1、获取该方案优惠金额
            Integer discountAmount = discountDTO.getDiscountAmount();
            int size = discountDTO.getCouponIds().size();
            //2.2、获取该方案用户券组合（按顺序拼接为String）
            String couponIds = discountDTO.getCouponIds().stream().sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            //2.3、更新bestAmountMap
            Integer oldDiscountAmount = bestAmountMap.get(couponIds) == null ? 0 : bestAmountMap.get(couponIds).getDiscountAmount();
            //如果不存在，或者当前优惠券组合优惠金额大于旧优惠券组合优惠金额，则更新bestAmountMap
            if (!bestAmountMap.containsKey(couponIds) || discountDTO.getDiscountAmount() > oldDiscountAmount) {
                bestAmountMap.put(couponIds, discountDTO);
            }
            int oldSize = bestCouponsMap.get(discountAmount) == null ? 0 : bestCouponsMap.get(discountAmount).getCouponIds().size();
            //2.4 更新bestCouponsMap
            if (!bestCouponsMap.containsKey(discountAmount) || size < oldSize) {
                //如果不存在，或者金额相同时优惠券使用张数更少，则更新bestAmountMap
                bestCouponsMap.put(discountAmount, discountDTO);
            }
        }
        //3、对两个类型的集合求value的并集
        Collection<CouponDiscountDTO> bestSolutions = CollUtils
                .intersection(bestAmountMap.values(), bestCouponsMap.values());
        //4、对bestSolution进行优惠金额降序排序，返回
        return bestSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());
    }

    /**
     * 计算优惠明细
     *
     * @param solution
     * @param courses
     * @param availableMap
     * @return
     */
    private CouponDiscountDTO calculateDiscount(
            List<Coupon> solution, List<OrderCourseDTO> courses, Map<Coupon, List<OrderCourseDTO>> availableMap) {
        //1、初始化CouponDiscountDTO
        CouponDiscountDTO couponDiscountDTO = new CouponDiscountDTO();
        //2、初始化优惠明细map
        //Map<K , V> key:课程id value:优惠金额(初始为0)
        Map<Long, Integer> discountMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, c -> 0));
        //3、遍历解决方案，计算优惠明细
        for (Coupon coupon : solution) {
            //3.1 获取优惠券对应的课程列表
            List<OrderCourseDTO> availableCourses = availableMap.get(coupon);
            //3.2计算该轮优惠前总价
            int totalAmount = availableCourses.stream().mapToInt(c -> c.getPrice() - discountMap.get(c.getId())).sum();
            //3.3判断是否满足优惠券使用门槛
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());//获取对应优惠券类型的优惠策略
            boolean canUser = discount.canUse(totalAmount, coupon);
            if (!canUser) {
                continue;//如果不满足优惠券使用门槛，略过该张优惠券的优惠计算
            }
            //3.4计算优惠金额
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            //3.5加权计算订单每个课程的本轮优惠明细  ,同时更新优惠明细map
            calculateDiscountDetails(discountMap, discountAmount, availableCourses, totalAmount);
            //3.6、更新couponDiscountDTO
            couponDiscountDTO.getCouponIds().add(coupon.getCreater());
            couponDiscountDTO.getRules().add(discount.getRule(coupon));
            couponDiscountDTO.setDiscountAmount(couponDiscountDTO.getDiscountAmount() + discountAmount);
        }
        //4、处理完后的优惠明细存入DTO
        couponDiscountDTO.setDiscountDetail(discountMap);
        //5、返回结果
        return couponDiscountDTO;
    }

    /**
     * 根据每轮优惠总额加权计算每个课程的优惠明细
     *
     * @param discountMap
     * @param discountAmount
     * @param availableCourses
     * @param totalAmount
     */
    private void calculateDiscountDetails(Map<Long, Integer> discountMap, int discountAmount, List<OrderCourseDTO> availableCourses, int totalAmount) {
        //1、遍历本轮优惠计算优惠了的课程
        int count = 0;
        int totalDiscount = 0;
        for (OrderCourseDTO availableCourse : availableCourses) {
            count++;
            int discount;
            //判断是否是最后一件课程商品
            if (!(count == availableCourses.size())) {
                //1.1计算本轮优惠计算中，该课程对应的优惠金额
                discount = discountAmount * availableCourse.getPrice() / totalAmount;
                totalDiscount += discount;
            } else {
                discount = discountAmount - totalDiscount;
            }
            //1.2更新优惠明细map
            discountMap.put(availableCourse.getId(), discountMap.get(availableCourse.getId()) + discount);
        }
    }
}
